package com.achen.study.study1;

import java.util.concurrent.*;

/**
 * @description: 初始化线程的四种方式
 * @author: Zhaoliang Chen
 * @time: 2021/8/3
 */
public class Study1 {

    //初始化线程的四种方式
    //1、继承Thread
    //2、实现Runnable接口
    //3、实现Callable接口 + FutureTask (可以拿到返回结果，可以处理异常)
    //4、线程池

    //1、2 主进程都无法获取子进程的运算结果，也无法控制服务器的性能损耗
    //3、主进程可以获取子线程的运算结果，但是也无法控制服务器的性能损耗
    //4、线程池的优点:降低资源的消耗、提高响应速度、提高线程的可管理性



    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //1、继承Thread的方式创建新的线程，异步执行任务
//        System.out.println("主线程执行开始: " + Thread.currentThread().getId());
//        TestExtendThread testExtendThread = new TestExtendThread();
//        testExtendThread.start();
//        System.out.println("主线程执行结束: " + Thread.currentThread().getId());


        //2、实现Runnable的方式创建新的线程，异步执行任务
//        System.out.println("主线程执行开始: " + Thread.currentThread().getId());
//        Thread thread = new Thread(new TestImplementRunnable());
//        thread.start();
//        System.out.println("主线程执行结束: " + Thread.currentThread().getId());


        //3、实现Callable接口 + FutureTask的方式实现执行任务
//        System.out.println("主线程执行开始: " + Thread.currentThread().getId());
//        FutureTask<String> stringFutureTask = new FutureTask<String>(new TestImplementsCallable());
//        Thread thread = new Thread(stringFutureTask);
//        thread.start();
//        String str = stringFutureTask.get();
//        System.out.println("获得子线程的返回值: " + str);
//        System.out.println("主线程执行结束: " + Thread.currentThread().getId());


        //4、创建线程池的方式又分为以下几种，这里我们就简单带过前面几种简单的
        //JDK5往后，任务分两类：一类是实现了Runnable接口的类，一类是实现了Callable接口的类。两者都可以被ExecutorService执行，
        //第二种就是我们创建自定义线程池


        //4.1、通过Executors工具类可以创建三种类型的普通线程池：第一种

        //使用于为了满足资源管理需求而需要限制当前线程数量的场合。使用于负载比较重的服务器。
/*        ExecutorService executorService = Executors.newFixedThreadPool(5);//固定大小的线程池
        System.out.println("主线程执行开始: " + Thread.currentThread().getId());*/

/*        Future<Object> future = executorService.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                try {
                    Thread.sleep(1000);
                    System.out.println("【submit】子线程在停止1S后执行完毕: " + Thread.currentThread().getId());

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "实现Callable接口，并通过Submit提交任务的方式可以获得返回值";
            }
        });
        // future.get() 会阻塞当前线程，直到拿到异步线程的响应值的时候才会，放行
        System.out.println("我在等异步的响应 : " + future.get());
        System.out.println("主线程执行结束: " + Thread.currentThread().getId());
        executorService.shutdown();*/

        //大家点进去submit方法看一下，一共有三个重载的方法，他们分别是：
        //TODO 1、     <T> Future<T> submit(Callable<T> task);
        //能获取到它的返回值，通过future.get()获取（阻塞直到任务执行完）
        //TODO 2、     <T> Future<T> submit(Runnable task, T result);
        //能通过传入的载体result间接获得线程的返回值
        //TODO 3、     Future<?> submit(Runnable task);
        //是没有返回值的，就算获取它的返回值也是null

/*        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("【execute】子线程在停止1S后执行完毕: " + Thread.currentThread().getId());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //和submit一样，大家点进去看一下，就会发现就是个孤儿方法 : execute()，执行一个任务，没有返回值。
        System.out.println("主线程执行结束: " + Thread.currentThread().getId());
        executorService.shutdown();*/


        //4.2、通过Executors工具类可以创建三种类型的普通线程池：第二种
        //需要保证顺序执行各个任务的场景
//        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();//单线程池
//        System.out.println("主线程执行开始: " + Thread.currentThread().getId());
//        Future<Object> haveResultFuture = singleThreadExecutor.submit(new Callable<Object>() {
//            @Override
//            public Object call() throws Exception {
//                try {
//                    Thread.sleep(1000);
//                    System.out.println("【submit】子线程在停止1S后执行完毕: " + Thread.currentThread().getId());
//
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                return "实现Callable接口，并通过Submit提交任务的方式可以获得返回值";
//
//            }
//        });
//
//        singleThreadExecutor.execute(new Runnable() {
//            @Override
//            public void run() {
//                try {
//                    Thread.sleep(1000);
//                    System.out.println("【execute】子线程在停止1S后执行完毕: " + Thread.currentThread().getId());
//
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        });
//
//        System.out.println("获得响应值" + haveResultFuture.get());
//        System.out.println("主线程执行结束: " + Thread.currentThread().getId());
//        singleThreadExecutor.shutdown();



        //4.3、通过Executors工具类可以创建三种类型的普通线程池：第三种

/*        //当提交任务速度高于线程池中任务处理速度时，缓存线程池会不断的创建线程,适用于提交短期的异步小程序，以及负载较轻的服务器
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();//缓存线程池
        //与上同理*/



        //4.4 自定义线程池 : ThreadPoolExecutor
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                4,                                  //核心线程数
                100,                            //最大线程数量；控制资源
                10L,                               //存活时间。如果当前的线程数量大于 core数量。释放空闲的线程
                TimeUnit.SECONDS,                                //时间单位
                new LinkedBlockingDeque<Runnable>(1000), //阻塞队列。如果任务有很多，就会将目前多的任务放到队列里面。只要有线程空闲，就会去队列里面取出新的任务继续执行。
                Executors.defaultThreadFactory(),                 //线程的创建工厂
                new ThreadPoolExecutor.AbortPolicy()              //拒绝策略，如果队列满了，按照我们指定的拒绝策略拒绝执行任务。
                );


        //阻塞队列,常用的阻塞队列有
            //ArrayBlockingQueue 　　　　　　//基于数组的先进先出队列，此队列创建时必须指定大小；
            //LinkedBlockingQueue　　　　　　//基于链表的先进先出队列，如果创建时没有指定此队列大小，则默认为Integer.MAX_VALUE,服务器OOM
            //synchronousQueue　　　　　　　　//这个队列比较特殊，它不会保存提交的任务，而是将直接新建一个线程来执行新来的任务。
        //线程的创建工厂
            //线程工厂。用来为线程池创建线程，当我们不指定线程工厂时，线程池内部会调用Executors.defaultThreadFactory()创建默认的线程工厂
            //其后续创建的线程优先级都是Thread.NORM_PRIORITY。如果我们指定线程工厂，我们可以对产生的线程进行一定的操作
        //拒绝策略有以下四种:
            //ThreadPoolExecutor.AbortPolicy:　　　　　　　　 // 丢弃任务并抛出RejectedExecutionException异常。
            //ThreadPoolExecutor.DiscardPolicy：　　　　　　　// 也是丢弃任务，但是不抛出异常。
            //ThreadPoolExecutor.DiscardOldestPolicy：　　  // 丢弃队列最前面的任务，然后重新尝试执行任务（重复此过程）
            //ThreadPoolExecutor.CallerRunsPolicy：　　　　  // 由调用线程处理该任务


        //工作顺序:
        //1、线程池创建，准备好core数量的核心线程，准备接受任务
        //2、core满了，就将再进来的任务放入阻塞队列中。空闲的core就会自己去阻塞队列获取任务
        //3、阻塞队列满了，就直接开新线程执行，最大只能开到max指定的数量
        //4、max满了就用 RejectedExecutionHandler handler 拒绝任务
        //5、max都执行完成，有很多空闲，在指定的时间 keepAliveTime 以后，释放 max - core 这些线程
        //6、new LinkedBlockingDeque<>()：默认是 Integer 的最大值。会导致内存占满，需要根据自己的业务定


    }
}
